package com.atguigu.gmall.common.config;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.model.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

/**
 * @Author atguigu-mqx
 * @Description TODO
 * @Date 2023/7/25 10:21
 * @Version 1.0
 */
@Slf4j
@Component
public class MQProducerAckConfig implements RabbitTemplate.ReturnCallback,RabbitTemplate.ConfirmCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedisTemplate redisTemplate;
    //  让当前这个类与RabbitTemplate 有关系！
    @PostConstruct
    public void init(){
        //  this：表示当前类的对象 MQProducerAckConfig
        rabbitTemplate.setReturnCallback(this);
        rabbitTemplate.setConfirmCallback(this);
    }
    /**
     * 判断消息是否到交换机
     * @param correlationData  { id Message returnedMessage }
     * @param ack   true: 表示消息到交换机了，false:消息没有到交换机
     * @param relayText 表示信息提示
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String relayText) {
        //  判断
        if (ack){
            //  不需要重试
            log.info("信息到交换机{}",correlationData);
            System.out.println("消息到交换机{}," + correlationData);
        } else {
            log.error("消息没有到交换机:{}",relayText);
            //    如果消息未到交换机就重复.
            this.retryMsg(correlationData);
        }
    }


    /**
     * 判断消息是否到队列. 当消息没有到队列的时候，才会执行这个方法.
     * @param message   消息对象
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.error("消息路由queue失败，应答码={}，原因={}，交换机={}，路由键={}，消息={}",
                replyCode, replyText, exchange, routingKey, message.toString());

        //  消息没有到队列也需要重试. 可以从redis 中获取对象.
        //  this.redisTemplate.opsForValue().set(uuId, JSON.toJSONString(gmallCorrelationData),10, TimeUnit.MINUTES);
        //  获取uuId
        String uuId = message.getMessageProperties().getHeader("spring_returned_message_correlation");
        String gmallJson = (String) this.redisTemplate.opsForValue().get(uuId);
        //  需要将这个gmallJson 转换为GmallCorrelationData.class
        GmallCorrelationData gmallCorrelationData = JSON.parseObject(gmallJson, GmallCorrelationData.class);
        //  如果是延迟消息就停止.
        if (gmallCorrelationData.isDelay()) {
            return;
        }
        //  调用重试方法.
        retryMsg(gmallCorrelationData);
    }

    /**
     * 重试方法：
     * @param correlationData
     */
    private void retryMsg(CorrelationData correlationData) {
        //  先获取缓存中的数据--有重试次数.
        GmallCorrelationData gmallCorrelationData = (GmallCorrelationData) correlationData;
        int retryCount = gmallCorrelationData.getRetryCount();
        //  判断重试次数，还是否需要继续重复发送消息.
        if (retryCount>=3){
            log.error("重试次数已到{}",+retryCount);
            //  需要将消息持久化到数据库,后续由人工处理. insert into msg values ();
        } else {
            //  修改重试次数
            retryCount++;
            //  写回缓存.
            gmallCorrelationData.setRetryCount(retryCount);
            this.redisTemplate.opsForValue().set(gmallCorrelationData.getId(), JSON.toJSONString(gmallCorrelationData),10, TimeUnit.MINUTES);
            //  判断发送消息类型
            if (gmallCorrelationData.isDelay()){
                //  发送延迟消息.
                this.rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),gmallCorrelationData.getRoutingKey(),gmallCorrelationData.getMessage(),
                        message -> {
                            //  设置延迟时间
                            message.getMessageProperties().setDelay(gmallCorrelationData.getDelayTime()*1000);
                            return message;
                        },gmallCorrelationData);
            } else {
                //  进行重试,再次发送消息.
                this.rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),gmallCorrelationData.getRoutingKey(),gmallCorrelationData.getMessage(),
                        gmallCorrelationData);
            }
        }
    }
}
